// Dwarf.Unmanaged.Serial.cpp : Defines the entry point for the console application.
//

#include "Dwarf.Unmanaged.Mpi.h"

using namespace std;

// The settings for this dwarf.
static Settings* settings;

// Get settings from the command line.
static int getSettings(int argc, char** argv, Solver *sgsolver) 
{

    int error = 0;
    if (sgsolver->isRootThread) 
    {
        settings = new Settings();
        if (settings->init(argc,argv,sgsolver))         //Parse all arguments for dwarf.
        {            
            delete settings;
            error = 1;
        }

        sgsolver->size = sgsolver->ymax;
    }    

    //Distribute error if that occurred while parsing.
    MPI_Bcast(&error, 1, MPI_INT, NUMBER_ROOT_PROCESS, MPI_COMM_WORLD); 

    if (error) return -1;

    //Distribute number of columns.
    MPI_Bcast(&sgsolver->xmax, 1, MPI_INT, NUMBER_ROOT_PROCESS, MPI_COMM_WORLD);  
    //Distribute number of rows.
    MPI_Bcast(&sgsolver->ymax, 1, MPI_INT, NUMBER_ROOT_PROCESS, MPI_COMM_WORLD);
    //Distribute epsilon.
    MPI_Bcast(&sgsolver->epsilon, 1, MPI_DOUBLE, NUMBER_ROOT_PROCESS, MPI_COMM_WORLD);

    int count = (sgsolver->ymax - 2) / sgsolver->commSize;      

    MPI_Status status;
    int i, j;

    if (sgsolver->isRootThread)
    {        
        for (i = 1; i < sgsolver->commSize; i ++) 
        {

            //Distribute values of nodes to their process
            for (j = 0; j < count + 2; j ++)
                MPI_Send(
                    sgsolver->grid[j + count * i + (sgsolver->ymax - 2) % sgsolver->commSize],
                    sgsolver->xmax,
                    MPI_DOUBLE,
                    i,
                    i + j,
                    MPI_COMM_WORLD);            
        }

        //Init arrays
        for (i = 0; i < count + 2 + (sgsolver->ymax - 2) % sgsolver->commSize; i ++)
        {
            for (j = 0; j < sgsolver->xmax; j ++) 
            {
                sgsolver->tempGrid[i][j] = sgsolver->grid[i][j];
            }
        }
        for (j = 0; j < sgsolver->xmax; j ++) 
        {
            sgsolver->tempGrid[sgsolver->ymax - 1][j] = sgsolver->grid[sgsolver->ymax - 1][j];
        }
    } 
    else 
    {
        //Init arrays
        sgsolver->grid = new double*[count + 2];
        sgsolver->tempGrid = new double*[count + 2];        

        for (i = 0; i < count + 2; i ++) 
        {
            sgsolver->grid[i] = new double[sgsolver->xmax];
            sgsolver->tempGrid[i] = new double[sgsolver->xmax];            
            for (j = 0; j < sgsolver->xmax; j ++)
            {
                sgsolver->grid[i][j] = sgsolver->tempGrid[i][j] = 0;
            }
        }

        //Receive values of nodes
        for (i = 0; i < count + 2; i ++) 
        {
            MPI_Recv(
                sgsolver->grid[i], 
                sgsolver->xmax, 
                MPI_DOUBLE, 
                NUMBER_ROOT_PROCESS, 
                sgsolver->commRank + i, 
                MPI_COMM_WORLD, 
                &status);
            for (j = 0; j < sgsolver->xmax; j ++) 
            {
                 sgsolver->tempGrid[i][j] = sgsolver->grid[i][j];
            }
        }
    }

    //Compute size of arrays to dispose.
    sgsolver->size = sgsolver->isRootThread ? sgsolver->ymax : count + 2 ;

    //MPI_Scatter(sgsolver->grid,sgsolver->xmax * sgsolver->ymax,MPI_DOUBLE,sgsolver->tempGrid,sgsolver->xmax * sgsolver->ymax,MPI_DOUBLE,NUMBER_ROOT_PROCESS,MPI_COMM_WORLD);

    return 0;
}

// Point of the program start.
void main(int argc, char** argv)
{   

    Solver* sgsolver = new Solver(); 

    if (sgsolver->commSize == 1) 
    {
        delete sgsolver;
        printf("Only one mpi process.");
        exit(-1);
    }

    if (getSettings(argc, argv, sgsolver))         // Get settings from the command line.
    {
        delete sgsolver;
        exit(-1);
    }

    MPI_Barrier(MPI_COMM_WORLD);

    if (sgsolver->isRootThread) 
    {
        settings->start();                          // Start new time count.
    }

	sgsolver->solve();                             // Solve the current problem.

    MPI_Barrier(MPI_COMM_WORLD);

    if (sgsolver->isRootThread) 
    {
        settings->finish(sgsolver);                // Stop the time count and write results.
        delete settings;
    }

    delete sgsolver;
}